#pragma once
#include <list>
#include "mysql.hpp"
#include "room.hpp"
#include "log.hpp"

template <class T>
class MatchQueue
{
private:
    std::list<T> _queue;
    std::condition_variable _cond;
    std::mutex _mutex;

public:
    MatchQueue()
    {
    }
    void wait()
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _cond.wait(lock);
    }
    void push(T val)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _queue.push_back(val);
        _cond.notify_all();
    }
    void remove(T val)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _queue.remove(val);
    }
    int size()
    {
        std::unique_lock<std::mutex> lock(_mutex);
        return _queue.size();
    }
    bool pop(T &val)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        if (_queue.size() == 0)
            return false;
        val = _queue.front();
        _queue.pop_front();
        return true;
    }
};

class Match
{
private:
    MatchQueue<uint32_t> _rookie_queue;
    MatchQueue<uint32_t> _normal_queue;
    MatchQueue<uint32_t> _master_queue;
    RoomManage *_room_mg;
    MysqlUserTable *_user_tb;
    OnlineUser *_online_user;

    std::thread _rookie;
    std::thread _normal;
    std::thread _master;

    void handlerMatch(MatchQueue<uint32_t> &queue)
    {
        while (1)
        {
            while (queue.size() < 2)
            {
                queue.wait();
            }
            uint32_t user1, user2;
            if (queue.pop(user1) == false)
                continue;
            if (queue.pop(user2) == false)
            {
                queue.push(user1);
                continue;
            }
            if (_online_user->findInHall(user1) == false)
            {
                Log(WARNING, "one user is not online, again");
                queue.push(user2);
                continue;
            }
            if (_online_user->findInHall(user2) == false)
            {
                Log(WARNING, "one user is not online, again");
                queue.push(user1);
                continue;
            }
            roomPtr rPtr = _room_mg->createRoom(user1, user2);

            Json::Value val;
            val["optype"] = "match_success";
            val["result"] = true;
            std::string response;
            JsonUtil::Serialization(val, response);
            if (_online_user->findInHall(user1))
            {
                websocketServer::connection_ptr wPtr = _online_user->idToConnFromhall(user1);
                wPtr->send(response);
                Log(INFO, "send to uid : %d", user1);
            }
            if (_online_user->findInHall(user2))
            {
                websocketServer::connection_ptr bPtr = _online_user->idToConnFromhall(user2);
                bPtr->send(response);
                Log(INFO, "send to uid : %d", user2);
            }
        }
    }
    void rookiehandler() { handlerMatch(_rookie_queue); }
    void normalhandler() { handlerMatch(_normal_queue); }
    void masterhandler() { handlerMatch(_master_queue); }

public:
    Match(MysqlUserTable *tb, OnlineUser *online, RoomManage *rm)
        : _user_tb(tb), _online_user(online), _room_mg(rm), _rookie(&Match::rookiehandler, this), _normal(&Match::normalhandler, this), _master(&Match::masterhandler, this)
    {
    }
    bool addPlayer(uint32_t uid)
    {
        Json::Value ret;
        if (_user_tb->getById(uid, ret) == false)
        {
            Log(WARNING, "get user from id failed, the id is : %d", uid);
            return false;
        }
        int score = ret["score"].asInt();
        if (score < 2000)
            _rookie_queue.push(uid);
        else if (score >= 2000 && score < 3000)
            _normal_queue.push(uid);
        else if (score >= 3000)
            _master_queue.push(uid);
        return true;
    }
    bool removePlayer(uint32_t uid)
    {
        Json::Value ret;
        if (_user_tb->getById(uid, ret) == false)
        {
            Log(WARNING, "get user from id failed, the id is : %d", uid);
            return false;
        }
        int score = ret["score"].asInt();
        if (score < 2000)
            _rookie_queue.remove(uid);
        else if (score >= 2000 && score < 3000)
            _normal_queue.remove(uid);
        else if (score >= 3000)
            _master_queue.remove(uid);
        return true;
    }
};